package com.learn.netty.nio;

import com.learn.netty.bio.multithread.PrintUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * 基于事件驱动，一个线程可以服务多个客户端的连接
 */
public class NIOServer {
    private static int PORT = 8081;

    public static void main(String[] args) {
        Selector selector = null;
        ServerSocketChannel serverSocketChannel = null;
        SocketChannel socketChannel = null;

        try {
            serverSocketChannel = ServerSocketChannel.open();
            selector = Selector.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.bind(new InetSocketAddress(PORT));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (!Thread.interrupted()) {
                PrintUtils.printLog(String.format("start selector select port %d ", PORT));
                selector.select();
                Set<SelectionKey> selected = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selected.iterator();
                if (iterator.hasNext()) {
                    handler(iterator.next(), selector);
                }
                selected.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        } finally {
            try {
                if (serverSocketChannel != null) serverSocketChannel.close();
                if (socketChannel != null) socketChannel.close();
                if (selector != null) selector.close();
                if (serverSocketChannel != null) serverSocketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void handler(SelectionKey selectionKey, Selector selector) {
        try {
            if (selectionKey.isValid()) {
                if (selectionKey.isAcceptable()) {
                    PrintUtils.printLog("doAccept");
                    doAccept(selectionKey, selector);
                }
                if (selectionKey.isReadable()) {
                    PrintUtils.printLog("doRead");
                    doRead(selectionKey);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void doRead(SelectionKey selectionKey) {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        SocketAddress remoteAddress = null;
        try {
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            int readBytes = socketChannel.read(byteBuffer);
            if (readBytes > 0) {
                Thread.sleep(100);
                byteBuffer.flip();
                byte[] bytes = new byte[byteBuffer.remaining()];
                byteBuffer.get(bytes);
                remoteAddress = socketChannel.getRemoteAddress();
                PrintUtils.printLog(String.format("recv: %s from %s", new String(bytes, "UTF-8"), remoteAddress));
                // send
                String sendMsg = "OK";
                doWrite(socketChannel, sendMsg);
            } else if (readBytes == -1) {
                PrintUtils.printLog(String.format("close connection %s", remoteAddress));
                socketChannel.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
            try {
                socketChannel.close();
                PrintUtils.printLog(String.format("close connection %s", remoteAddress));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void doWrite(SocketChannel socketChannel, String sendMsg) throws IOException {
        byte[] writeBytes = sendMsg.getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(writeBytes.length);
        writeBuffer.put(writeBytes);
        writeBuffer.flip();
        socketChannel.write(writeBuffer);
        PrintUtils.printLog(String.format("send msg to %s", socketChannel.getRemoteAddress()));
    }

    private static void doAccept(SelectionKey selectionKey, Selector selector) throws IOException {
        ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
        SocketChannel sc = ssc.accept();
        PrintUtils.printLog(String.format("connect seccess %s", sc.getRemoteAddress()));
        sc.configureBlocking(false);
        sc.register(selector, SelectionKey.OP_READ);
    }

}
